<?php
/**
 * Created by PhpStorm.
 * User: meng
 * Date: 2021/7/5
 * Time: 9:28 下午
 */
namespace app\utils;

use app\lib\exception\AuthException;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\ValidationData;
use Ramsey\Uuid\Uuid;
use think\facade\Config;
use think\facade\Request;
use \Exception;

class JwtUtils
{
    // token过期时间
    private $expire = 7200;
    // 刷新token过期时间
    private $refresh_expire = 259200;
    // token秘钥
    private $secret = 'secret';
    // 刷新token秘钥
    private $refresh_secret = 'refresh_secret';

    private $builder;
    private $signer;
    private $issue;  // jwt签发者iss
    private $audience; // 接收jwt的一方aud

    public function __construct ($configure = []) {
        $this->builder = new Builder();
        $this->signer = new Sha256();
        if (empty($configure)) {
            $config = Config::get('jwt');
        } else {
            $config = $configure;
        }
        foreach ($config as $k => $v) {
            $this->$k = $v;
        }
        $this->issue = Request::domain();
    }

    public function awardToken ($userId, int $keepLogin = null): array {
        $jti = $this->createJti();
        $refreshJti = $this->createJti();
        $refreshExpire = !empty($keepLogin) && $keepLogin > 0 ? $keepLogin : $this->refresh_expire;
        return [
            'token' => $this->createToken($userId, $jti, $this->secret, $this->expire),
            'refresh_token' => $this->createToken($userId, $refreshJti, $this->refresh_secret, $refreshExpire)
        ];
    }

    public function validatToken ($token, $isRefresh = false) {
        try {
            $parse = (new Parser())->parse($token);
        } catch (Exception $e) {
            throw new AuthException(['msg' => '令牌失效']);
        }
        $this->verifyToken($token, $isRefresh);
        return $parse->getClaim('uid');
    }

    public function verifyToken ($token, $isRefresh = false): bool {
        if (empty($token)) {
            throw new AuthException(['msg' => '令牌失效']);
        }
        try {
            $parser = (new Parser())->parse($token);
        } catch (Exception $e) {
            throw new AuthException(['msg' => '令牌失效']);
        }
        if ($parser->isExpired() && !$isRefresh) {
            throw new AuthException(['msg' => '令牌已过期', 'errorCode' => 10003]);
        }
        $valid = new ValidationData();
        $aud = $parser->getClaim('aud');
        $iss = $parser->getClaim('iss');
        $jwt_id = $parser->getClaim('jti');
        $valid->setIssuer($iss);
        $valid->setAudience($aud);
        $valid->setId($jwt_id);
        $secret = $isRefresh ? $this->refresh_secret : $this->secret;
        if (!$parser->verify($this->signer, $secret)) {
            throw new AuthException(['msg' => '令牌失效']);
        }
        if (!$parser->validate($valid)) {
            throw new AuthException(['msg' => '令牌失效']);
        }
        return true;
    }

    public function refreshToken ($refreshToken) {
        $valid = $this->validatToken($refreshToken, true);
        if ($valid !== false) {
            $jti = $this->createJti();
            $userid = $valid;
            $token = $this->createToken($userid, $jti, $this->secret, $this->expire);
            return [
                'token'         => $token,
                'refresh_token' => $refreshToken
            ];
        }
        return false;
    }

    private function createToken ($userId, $jti, $secret, $expire): string {
        $time = time();
        return (string) $this->builder->issuedBy($this->issue) // iss jwt签发者
            ->permittedFor($this->audience) // aud 接收jwt的一方
            ->identifiedBy($jti) // jti jwt的唯一身份标识。
            ->issuedAt($time) // iat jwt的签发时间
            ->canOnlyBeUsedAfter($time) // nbf: 定义在什么时间之前，该jwt都是不可用的
            ->expiresAt($time + $expire)
            ->withClaim('uid', $userId)
            ->getToken($this->signer, new Key($secret));
    }

    private function createJti (): string {
        return (string) Uuid::uuid4();
    }
}
